import csv
import math
import os
import time
from datetime import datetime

import numpy as np
from openpyxl import Workbook
from simpy import Environment

from osc02.resource.MonitoredFilterStore import MonitoredFilterStore
from osc02.resource.Resources import ResourceUtil
from osc02.resource.stakeholders import ConstructionSite
from osc02.static.constants import Constants, Debugs, DirectoryConfig
from osc02.utils.cpm_util import get_schedule
from osc02.utils.random_util import RandomUtil


def get_cost_with_simulation(start_correction,
                             seed=None,  # NOT used
                             unit_waiting_penalty=Debugs.unit_component_late_acceptance_penalty_by_day,
                             unit_late_delivery_penalty=Debugs.unit_late_delivery_penalty_by_day,
                             unit_project_late_completion_penalty=Debugs.unit_project_late_completion_penalty_by_day,
                             project_deadline=Debugs.project_deadline,
                             debug=False
                             ):
    now = datetime.now()
    #should be invoked outside the method, since it a set of constants (CPM)
    # print('delivery_time_offset=', delivery_time_offset)
    activities, prj_start_date = get_schedule()
    # print("prj_start_date=", prj_start_date)
    scenario_num = 20
    sum_additional_cost_every_scenario = 0
    total_additional_cost_every_scenario_in_detail = [0, 0, 0]
    if debug:# just calculate the detailed total cost from scenarios and record
        filename_x_and_cost = os.path.join(
            DirectoryConfig.OUTPUT_DIR,'x_and_cost.csv')
        with open(filename_x_and_cost, 'a', newline='') as records:
            filename_x_and_cost_summary = os.path.join(
                DirectoryConfig.OUTPUT_DIR, 'x_and_cost_summary.csv')
            with open(filename_x_and_cost_summary, 'a', newline='') as summary_lines:
                writer_sum = csv.writer(summary_lines)
                writer_sum.writerow(["Date", "Hr", "Min",
                                     "max_total", "min_total", "mean_total",
                                     "std_total", "Coeff. of Var.", "max_project_delay_days"])
                writer_detail = csv.writer(records)
                writer_detail.writerow(["Date", "Hr", "Min", "NO.", "total_waiting_cost", "total_late_delivery_penalty",
                                        "project_late_completion_penalty", "average cost",
                                        "FL01", "FL02", "FL03", "FL04", "FL05", "FL06", "FL07",
                                        "FL08", "FL09", "FL10", "FL11", "FL12", "FL13", "FL14",
                                        "FL15", "FL16", "FL17", "FL18"])
                # writer.writerow(start_correction)
                # writer.writerow(['Scenario no', 'Additional cost'])
                cost_list = []
                for i in range(scenario_num):
                    # get
                    total_waiting_cost, total_late_delivery_penalty, project_late_completion_penalty \
                        = simulate(activities, debug, project_deadline, i + 1, start_correction,
                                   unit_late_delivery_penalty,
                                   unit_project_late_completion_penalty, unit_waiting_penalty)
                    total = total_waiting_cost - total_late_delivery_penalty + project_late_completion_penalty
                    print("Debug=True, seed=", i, "\ttotal=", total)
                    writer_detail.writerow([now.strftime("%y-%m-%d"), now.strftime("%H"), now.strftime("%M"),
                                            i + 1, total_waiting_cost,
                                            -total_late_delivery_penalty,
                                            project_late_completion_penalty,
                                            total])

                    cost_list.append([total_waiting_cost,
                                      total_late_delivery_penalty,
                                      project_late_completion_penalty,
                                      total_waiting_cost -
                                      total_late_delivery_penalty +
                                      project_late_completion_penalty
                                      ])

                    # sum the detailed total cost from scenarios
                    total_additional_cost_every_scenario_in_detail = [m + n for m, n in
                                                                      zip(
                                                                          total_additional_cost_every_scenario_in_detail,
                                                                          [total_waiting_cost,
                                                                           total_late_delivery_penalty,
                                                                           project_late_completion_penalty])]
                np_array = np.array(cost_list)
                # max of total produced with the proposed schedule on the scenarios
                print("np_array:", np_array)
                max_total = np.max(np_array[:, 3])
                min_total = np.min(np_array[:, 3])
                mean_total = np.mean(np_array[:, 3])
                std_total = np.std(np_array[:, 3])
                max_project_completion_delay = np.max(np_array[:, 2]) / Debugs.unit_project_late_completion_penalty_by_day
                to_print = [now.strftime("%y-%m-%d"), now.strftime("%H"), now.strftime("%M"), max_total, min_total,
                            mean_total, std_total, f"{std_total / mean_total:.1f}", max_project_completion_delay]
                to_print.extend(start_correction)
                writer_sum.writerow(to_print)

            total_average_cost = (total_additional_cost_every_scenario_in_detail[0] -
                                  total_additional_cost_every_scenario_in_detail[1] +
                                  total_additional_cost_every_scenario_in_detail[2]) / scenario_num

            cost_ = [now.strftime("%y-%m-%d"), now.strftime("%H"), now.strftime("%M"),
                     "AVE", total_additional_cost_every_scenario_in_detail[0] / scenario_num,
                     -total_additional_cost_every_scenario_in_detail[1] / scenario_num,
                     total_additional_cost_every_scenario_in_detail[2] / scenario_num,
                     total_average_cost]
            # cost_.extend(start_correction)
            writer_detail.writerow(cost_)
            writer_detail.writerow(start_correction)
            print("Debug=True, average total=", total_average_cost)

        return total_additional_cost_every_scenario_in_detail[0] / scenario_num, \
               total_additional_cost_every_scenario_in_detail[1] / scenario_num, \
               total_additional_cost_every_scenario_in_detail[2] / scenario_num
    else:
        # simulate the scenarios
        for i in range(scenario_num):
            additional_cost = simulate(activities, debug, project_deadline, i + 1, start_correction,
                                       unit_late_delivery_penalty,
                                       unit_project_late_completion_penalty, unit_waiting_penalty)
            # print("seed= ", i, "cost= ", additional_cost)
            sum_additional_cost_every_scenario += additional_cost
    # print("debug=False, average cost=", sum_additional_cost_every_scenario / scenario_num)
    return sum_additional_cost_every_scenario / scenario_num


def simulate(activities, debug, project_deadline, seed, start_correction, unit_late_delivery_penalty,
             unit_project_late_completion_penalty, unit_waiting_penalty):
    env = Environment()
    # storage can hold 200 components
    storage_yard = MonitoredFilterStore(env, capacity=200)
    # 1 worker team
    assembly_worker_teams = ResourceUtil.get_stations(env, capacity=1)
    # grout_worker_teams = ResourceUtil.get_stations(env, capacity=1)
    grout_gears = ResourceUtil.get_stations(env, capacity=1)
    construction_site1 = ConstructionSite(env=env, name='Site1',
                                          storage_yard=storage_yard,
                                          worker_teams=assembly_worker_teams,
                                          grout_gears=grout_gears,
                                          crane_num=1,
                                          activities=activities)
    # get sample data (decimals)
    smp_delivery_delay, smp_duration = get_sampled_duration_and_delivery_time(seed)
    # set sample duration and delivery
    index = 0
    for activity in activities:
        activity.construction_site = construction_site1
        activity.completion_event = env.event()
        activity.component_delivered_event = env.event()
        # actual duration is set according to sample data
        activity.act_duration = math.ceil(smp_duration[index])
        # skd_delivery_time + chromosome representing receiving postponement + delivery delay
        # activity.skd_delivery_time = max(0, activity.es + start_correction[index])
        # scheduled delivery time is used as scheduled erection time
        activity.skd_delivery_time = activity.es + start_correction[index]
        # ensure the scheduled erection time non-negative
        if activity.skd_delivery_time < 0:
            activity.skd_delivery_time = 0
        # print("skd_delivery_time=",activity.es, start_correction[index], activity.skd_delivery_time)
        activity.act_delivery_time = activity.skd_delivery_time + max(0, math.ceil(smp_delivery_delay[index]))
        index += 1
    # the act_delivery_time has been preset, no need to process.
    counter = 1
    for activity in activities:
        if counter < 97:  # now, the statement is redundant as the number of activities is less than 96.
            env.process(activity.transport())
            counter += 1
    counter = 1
    for activity in activities:
        if counter < 97:
            p = env.process(activity.act())
            counter += 1
    #
    env.run(until=2000)

    if debug:
        filename = 'Project process simulation' + str(seed) + '.xlsx'
        filename = os.path.join(DirectoryConfig.OUTPUT_DIR, filename)
        # print(filename)
        if os.path.exists(filename):
            os.remove(filename)

        wb = Workbook()
        # ws = wb.remove_sheet
        ws = wb.active
        title = ['name', 'es', 'duration', 'chromo.', 'skd_delivery_time', 'act_delivery_time',
                 'act_erection_start', 'act_duration', 'delivery delay',
                 'start delay', 'waiting cost', 'penalty for cmp. deli.', 'penalty for prj. deli.']
        ws.append(title)
    # print(env.now, construction_site1)
    total_waiting_cost = 0
    total_late_delivery_penalty = 0
    project_act_completion = 0
    counter = 0
    for activity in activities:
        # print('act Start', activity.name, activity.act_erection_start, activity.act_delivery_time)
        total_waiting_cost += \
            unit_waiting_penalty * (activity.act_erection_start - activity.act_delivery_time)
        total_late_delivery_penalty += \
            unit_late_delivery_penalty * (activity.act_delivery_time - activity.skd_delivery_time)
        #
        current_act_completion = activity.act_erection_start + activity.act_duration
        # keep updating project_act_completion
        project_act_completion = max(project_act_completion, current_act_completion)
        if debug:
            component = [activity.name, round(activity.es, 2), activity.duration,
                         round(start_correction[counter]),  # 4 -- order of the element
                         round(activity.skd_delivery_time, 2), round(activity.act_delivery_time, 2),  # 5 6
                         round(activity.act_erection_start, 2), round(activity.act_duration, 2),  # 7 8
                         round(activity.act_delivery_time - activity.skd_delivery_time, 2),  # 9
                         round(activity.act_erection_start - activity.es, 2),  # 10
                         round(unit_waiting_penalty * (activity.act_erection_start - activity.act_delivery_time), 2),
                         # 11
                         round(unit_late_delivery_penalty * (activity.act_delivery_time - activity.skd_delivery_time),
                               # 12
                               2),
                         ]
            ws.append(component)
        counter += 1
        # print(activity.name, round(activity.es, 2), activity.duration,
        #       round(activity.skd_delivery_time, 2), round(activity.act_delivery_time, 2),
        #       round(activity.act_erection_start, 2),
        #       activity.act_duration)
    project_late_completion_penalty \
        = max(0, (project_act_completion - project_deadline)) * unit_project_late_completion_penalty
    # print(delivery_time_offset)
    # print(total_waiting_cost - total_late_delivery_penalty + project_late_completion_penalty,
    #       total_waiting_cost, total_late_delivery_penalty, project_late_completion_penalty)
    total_cost = total_waiting_cost - total_late_delivery_penalty + project_late_completion_penalty
    # to output
    if debug:
        summary = ['', '', '',
                   '',
                   '', '',
                   '', '',
                   'T. Cost=',
                   '=K20-L20+M20',
                   '=SUM(K2:K19)',
                   '=SUM(L2:L19)',
                   project_late_completion_penalty,
                   max(0, (project_act_completion - project_deadline))
                   ]
        ws.append(summary)
        wb.save(filename)
        wb.close()
        time.sleep(1.5)
    if debug:
        # print('delivery_time_offset=', delivery_time_offset)
        # print('debug costs=', total_cost, total_waiting_cost, total_late_delivery_penalty,
        #       project_late_completion_penalty,
        #       project_act_completion, project_deadline)
        return total_waiting_cost, total_late_delivery_penalty, project_late_completion_penalty
    else:
        # print('total costs=', total_cost, total_waiting_cost, total_late_delivery_penalty,
        #       project_late_completion_penalty, project_act_completion, project_deadline)
        return total_cost


# end of function simulate

# get sample data for floor duration and delivery  delay
def get_sampled_duration_and_delivery_time(seed):
    # print('seed=', seed)
    random_util = RandomUtil(seed, n_sf=Constants.N_ACTIVITY)
    # act_duration of walls of two types
    # act_duration = []
    # sample duration
    smp_duration = random_util.get_sf_erection_durations()
    # act_duration.extend(durations)
    # sample delivery delay
    smp_delivery_delay = random_util.get_component_delivery_delays()
    # print(act_delivery_delay)
    return smp_delivery_delay.tolist(), smp_duration


if __name__ == '__main__':
    # delivery_time_offset = [26, 25, 11, 6, 34, 60, 59, 68, 52, 74, 46, 34, 67, 68, 74, 100, 100, 100, 99, 100, 100, 35,
    #                         77, 58, 99, 100, 100,
    #                         83, 100, 100, 100, 100, 80, 100, 97, 100, 95, 94, 86, 78, 100, 91, 100, 100, 91, 94, 100,
    #                         100]
    # print(len(delivery_time_offset))
    # total_waiting_cost = get_cost_by_simulation(delivery_time_offset)
    # print(total_waiting_cost)
    #
    # delivery_time_offset = [54, 52, 33, 27, 58, 74, 76, 100, 100, 100, 85, 86, 94, 57,
    #                         87, 100, 82, 99, 77, 100, 100, 86, 100, 100, 97, 93, 100, 100,
    #                         88, 100, 100, 100, 100, 100, 43, 100, 100, 89, 82, 92, 99, 97,
    #                         100, 100, 100, 77, 69, 96]
    # print(len(delivery_time_offset))
    # total_waiting_cost = get_cost_by_simulation(delivery_time_offset)
    # print(total_waiting_cost)
    #
    # delivery_time_offset = [33, 16, 39, 43, 59, 59, 70, 75, 78, 71, 67, 46, 75, 90, 85, 100, 63, 94, 94, 100, 99,
    #                         100, 100, 100, 97, 90, 100, 82, 99, 97, 100, 85, 74, 100, 100, 100, 98, 89, 86, 100, 94, 65,
    #                         100, 100, 81, 97, 100, 100]
    # print(len(delivery_time_offset))
    # total_waiting_cost = get_cost_by_simulation(delivery_time_offset)
    # print(total_waiting_cost)

    print('base plan')
    # base plan
    delivery_time_offset = [0] * Constants.N_ACTIVITY
    for seed in range(1, 31):
        # act_delivery_delay, durations = get_randoms(seed=seed)
        # print(durations)
        total_waiting_cost = get_cost_with_simulation(start_correction=delivery_time_offset, seed=seed, debug=True)
        print(total_waiting_cost)
    print('####')
    # delivery_time_offset = [2, 3, 2, 3, 3, 3, 2, 1, 2, 3, 4, 3, 4, 4, 4, 4, 5, 5]
    # print(get_cost_with_simulation(delivery_time_offset=delivery_time_offset, seed=1))
    # delivery_time_offset = [1, 1, 2, 2, 3, 3, 2, 2, 4, 4, 5, 5, 5, 5, 6, 6, 7, 7]
    # print(get_cost_with_simulation(delivery_time_offset=delivery_time_offset, seed=1, debug=True))

    # delivery_time_offset = [0, 0, 2, 3, 3, 5, 4, 4, 4, 4, 5, 5, 5, 5, 6, 5, 6, 6]
    # print(get_cost_with_simulation(delivery_time_offset=delivery_time_offset, seed=1, debug=True))

    # delivery_time_offset = [0, 1, 2, 1, 3, 3, 2, 2, 3, 2, 3, 3, 3, 3, 4, 3, 4, 3]
    # print(get_cost_with_simulation(delivery_time_offset=delivery_time_offset, seed=1, debug=True))
